<template>
  <div class="module-test-event">
    <h1>{{ message }}</h1>
    <section>
        <h2>esModule</h2>
        <p>在es6中一个模块就是一个独立的文件，通过export可以抛出一个变量，通过import可以引入变量。import的是一个引用值，所以可以取到最新值。</p>
        <p>因为Module 的加载实现的是es6语法，所以在浏览器加载html文件时，需要在script 标签中加入type="module"属性,否则浏览器抛错：</p>
        <p>Uncaught SyntaxError: Cannot use import statement outside a module</p>
        <code>
            <pre>
                // 前提浏览器要支持 es6
                // a.js
                let abc = 1;

                export default function consoleLog () {
                    abc += 1;
                    console.error(abc);
                }

                let v1 = 'dai';

                export let v2 = 'hai';
                export {
                    v1 as streamV1,
                    abc as streamLatestVersion
                };

                // main.js
                &lt;script type="module"&gt;
                    import abc from './a.js';
                    console.error(abc); // 打印整个 consoleLog 函数
                    abc(); // 2
                    abc(); // 3  被累加

                    import {streamV1, streamLatestVersion} from './a.js';
                    console.error(streamV1); // dai
                    console.error(streamLatestVersion); // 3
                    import {v2 as me} from './a.js';
                    console.error(me); // hai
                &lt;/script&gt;

            </pre>
        </code>
        <p>import是静态引用，以下方式会报错</p>
        <code>
            <pre>
                // 报错
                import { 'f' + 'oo' } from 'my_module';

                // 报错
                let module = 'my_module';
                import { foo } from module;

                // 报错
                if (x === 1) {
                    import { foo } from 'module1';
                } else {
                    import { foo } from 'module2';
                }
            </pre>
        </code>
        <p>为了弥补不能动态加载，有一<a href="https://github.com/tc39/proposal-dynamic-import">提案</a>，建议引入import()函数，完成动态加载。会返回一个promise</p>
        <code>
            <pre>
                import(`./a.js`)
                .then(module => {
                    console.error(module);
                })
                .catch(err => {
                    console.error(err);
                });
            </pre>
        </code>
        <a href="http://caibaojian.com/es6/module.html">esModule参考文档</a>
        <h4>特点</h4>
        <p>原生支持，import的模块是引用值。服务器和浏览器只要支持es6均可使用，与commonjs规范不同，commonjs中获得的是运行结果的缓存且用于服务器，import是静态引用，所以不能使用表达式和变量，用import()弥补</p>
        <h4>循环引用</h4>
        <code>
            <pre>
                // a.js如下
                import {bar} from './b.js';
                console.log('a.js');
                console.log(bar);
                export let foo = 'foo';

                // b.js
                import {foo} from './a.js';
                console.log('b.js');
                console.log(foo);
                export let bar = 'bar';

                // babel  转换结果，执行结果
                b.js
                undefined
                a.js
                bar

                // 直接运行则会抛异常
            </pre>
        </code>
        <p>执行顺序</p>
        <ol>
            <li>进⼊a.js，执⾏import {bar} from './b.js'，进⼊b.js；</li>
            <li>在b.js中，企图通过import {foo} from './a.js'再次执行a.js，
                但是a.js已经被请求过被标记为了fetching状态，所以忽略不去执行，继续向下，
                此时输出要从a中拿出的foo,但是由于忽略了import，所以foo会不存在，所以会报foo没有声明，而babel 之后是undefined是由于做了转换_c = require("./c.js");有了定义，可以输出undefined;</li>
            <li>b.js 执行完毕继续执行a.js</li>
            <li>ps: b.js 改为 setTimeout(()=>{console.log(foo);}); 不会报错，因为在执行到console时a已经不是fetching 状态</li>
        </ol>
    </section>
    <section>
        <h2>commonJS</h2>
        <p>commonJS一般用于服务端，例如node,它是把整个的模块引入，生成一个内部的对象，再从这个对象上读取相应的方法。只有在运行时才知道这个对象是啥。多次引入只会执行一次，之后的引入会从缓存中获取。
            想要重新执行必须清理缓存。它是对输出值的拷贝，区别于esModule是值得引用。
        </p>
        <code>
            <pre>
                // CommonJS模块
                let { stat, exists, readFile } = require('fs');

                // 删除指定模块的缓存
                delete require.cache[moduleName];

                // 删除所有模块的缓存
                Object.keys(require.cache).forEach(function(key) {
                    delete require.cache[key];
                });
            </pre>
        </code>
        <p>对比 esmodule 结果</p>
        <code>
            <pre>
                // commonJs
                // lib.js
                var counter = 3;
                function incCounter() {
                    counter+=1;
                    console.error(counter);
                }
                module.exports = {
                    counter: counter,
                    incCounter: incCounter,
                };

                // main.js
                var mod = require('./lib.js');

                console.log(mod.counter);  // 3
                mod.incCounter();
                console.log(mod.counter); // 3

                // esModule
                // lib.js
                export let counter = 3;
                export function incCounter() {
                    counter+=1;
                }

                // main.js
                import { counter, incCounter } from './lib.js';
                console.log(counter); // 3
                incCounter();
                console.log(counter); // 4
            </pre>
        </code>
        <h4>循环引用</h4>
        <code>
            <pre>
                // a.js
                exports.done = false;
                var b = require('./b.js');
                console.log('在 a.js 之中，b.done = %j', b.done);
                exports.done = true;
                console.log('a.js 执行完毕');

                // b.js 
                exports.done = false;
                var a = require('./a.js');
                console.log('在 b.js 之中，a.done = %j', a.done);
                exports.done = true;
                console.log('b.js 执行完毕');

                // main.js 
                var a = require('./a.js');
                var b = require('./b.js');
                console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);

                // 结果
                在 b.js 之中，a.done = false
                b.js 执行完毕
                在 a.js 之中，b.done = true
                a.js 执行完毕
                在 main.js 之中, a.done=true, b.done=true

                // a.js  稍微改变一下，输出结果就不同
                var b = require('./b.js');
                exports.done = false;
                console.log('在 a.js 之中，b.done = %j', b.done);
                exports.done = true;
                console.log('a.js 执行完毕');

                // 输出结果
                在 b.js 之中，a.done = undefined
                b.js 执行完毕
                在 a.js 之中，b.done = true
                a.js 执行完毕
                在 main.js 之中, a.done=true, b.done=true
            </pre>
        </code>
        <p>执行顺序</p>
        <ol>
            <li>require('a.js');抛出done为false,引入b.js,</li>
            <li>开始执行b.js,done为false,require('a.js'),由于已经引入过a，所以跳过，执行console，由于a已经抛出了done是false,所以结果“在 b.js 之中，a.done = false”</li>
            <li>执行console,b.js执行结束，返回继续执行a.js</li>
            <li>执行a.js中console,执行结束</li>
        </ol>
        <p>不同于esmodule，根据引入位置类似于都整合到同一个文件，遇到一引入的跳过，然后从前到后顺序执行。同步的。</p>
    </section>
    <section>
        <h2>AMD</h2>
        <p>即commonJs以后，发展出适用于客户端的模块加载，commonJS是同步的，如果模块大的话会影响效率，之后就应运而生了适用于前端异步模块加载。
            推崇依赖前置，在模块定义的时候提前声明好所有的依赖，例如：RequireJS</p>
    </section>
    <section>
        <h2>CMD</h2>
        <p>和AMD解决问题想同，主要区别在于依赖就近，在需要模块的时候才去加载，例如：SeaJS</p>
        <a href="https://github.com/seajs/seajs/issues/277">SeaJS 与 RequireJS 的异同</a>
    </section>
    <section>
        <h3>参看文档</h3>
        <a href="https://sq.163yun.com/blog/article/170298273019912192">深入浅出 ES 模块（上篇）</a>
    </section>
    <p class="page-end">The End</p>
  </div>
</template>

<script lang="ts">
    import Vue from 'vue';
    import Component from 'vue-class-component';

    @Component({
        // 所有的组件选项都可以放在这里
    })
    export default class moduleExample extends Vue {
        // data 在这
        message: string = 'esModule AMD CMD 和 commomJS'
    }
</script>
<style lang="scss">
.module-test-event{
}
</style>